﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Windows.Resources;
using System.Windows.Markup;
using SmartPaginator.Extensions;

namespace SmartPaginator.Elements
{
    public class ListItemElement : ItemsControl, IBlockElement, IBlockContainer
    {
        #region Constructor

        protected ListItemElement(ListItem source)
        {
            Source = source;
            this.Template = ListItemElement.DefaultTemplate;

            SetupMarker();

        }

        public ListItemElement(ListItem source, IElementFactory factory): this(source)
        {
            if (source != null)
            {
                if (factory == null)
                    throw new ArgumentNullException("factory");

                foreach (Block block in source.Blocks)
                {
                    var element = factory.GetElementForContent(block);
                    this.Items.Add(element);
                }
            }
        }

        #endregion

        #region Properties

        public ListItem Source { get; private set; } 

        public IEnumerable<IBlockElement> Blocks 
        {
            get
            {
                return Items.Cast<IBlockElement>();
            }
        }

        public double ItemOffset { get; set; }
        public double MarkerOffset { get; set; }
        public string MarkerText { get; set; }
        public FontFamily MarkerFont { get; set; }
        public double MarkerSize { get; set; }
        public bool ShowMarker { get; set; }

        #endregion

        #region IBlockElement members

        public virtual void AddChildElement(UIElement child)
        {
            this.Items.Add(child);
        }

        public virtual IBlockElement GetEmptyCopy()
        {
            var result = new ListItemElement(this.Source);
            return result;
        }

        public IBlockElement GetPreviousBlock(IBlockElement block)
        {
            int index = Items.IndexOf(block as UIElement);
            if (index < 0)
                throw new ArgumentException("The provided block is not a child of this BlockContainer.");

            if (index == 0)
                return null;

            return Items[index - 1] as IBlockElement;
        }


        public HarvestResult Harvest(HarvestingContext context, double remainingHeight, IBlockElement previousElement)
        {
            var adjustedMargin = GetAdjustedMargin(previousElement);
            remainingHeight -= adjustedMargin.Top;

            bool isPartiallyHarvested = context.IsPartiallyHarvested(this);
            ListItemElement harvestedElement = null;

            IBlockElement previousChild = null;
            bool breakColumn = true; // Assume we can't fit the entire content. We'll update this to false if we get to the last child.
            foreach (UIElement child in this.Items)
            {
                if (context.IsFullyHarvested(child))
                    continue;


                var childBlock = child as IBlockElement;
                var childHarvestResult = childBlock.Harvest(context, remainingHeight, previousChild);
                remainingHeight = childHarvestResult.RemainingHeight;

                // Lets see if child fit
                if (childHarvestResult.HarvestedElement == null)
                {
                    return HarvestResult.BreakPage;
                }
                else
                {
                    if (harvestedElement == null)
                    {
                        harvestedElement = this.GetEmptyCopy() as ListItemElement;
                        harvestedElement.Margin = adjustedMargin;
                        if (isPartiallyHarvested)
                            harvestedElement.ShowMarker = false;
                    }

                    // Add child to harvested element
                    harvestedElement.AddChildElement(childHarvestResult.HarvestedElement);
                }

                previousChild = childBlock;
                if (this.Items.IndexOf(child) == Items.Count - 1)
                { 
                    // Last child. We are finished, if it is finished
                    if (context.IsFullyHarvested(child))
                    {
                        context.MarkAsFullyHarvested(this);
                        breakColumn = false;
                    }
                }
            }
            return new HarvestResult(harvestedElement, remainingHeight, breakColumn, false);
        }

        private Thickness GetAdjustedMargin(IBlockElement previousElement)
        {
            double topMargin = 0;

            if (previousElement != null)
            {
                // Get the bottom margin of the previous element
                topMargin = previousElement.CalculateBottomMargin();

                // Apply our margin instead if it is bigger
                var thisTop = CalculateTopMargin();
                if (thisTop > topMargin)
                    topMargin = thisTop;
            }
            else
            {
                if ((Source != null) && (!double.IsNaN(Source.Margin.Top)))
                    topMargin = Source.Margin.Top;
            }

            return new Thickness(this.Margin.Left, topMargin, this.Margin.Right, 0);
        }


        public virtual double CalculateBottomMargin()
        {
            return 0;
        }

        public virtual double CalculateTopMargin()
        {
            return 0;
        }

        public double CalculateHeight()
        {
            if (!double.IsNaN(Height))
                return Height;

            return CalculateContentHeight();
        }

        public double CalculateContentHeight()
        {
            double result = 0;

            foreach (var child in Items)
            {
                var element = child as FrameworkElement;
                if (element != null)
                {
                    result += element.Margin.Top + element.ActualHeight;
                }
            }

            return result;
        }

        public virtual double CalculateMinHeight()
        {
            if (Items.Count > 0)
            {
                var block = Items[0] as IBlockElement;
                if (block != null)
                    return block.CalculateMinHeight();
            }

            return 0;
        }

        #endregion

        #region DefaultTemplate Property

        public static ControlTemplate DefaultTemplate 
        {
            get
            {
                if (_DefaultTemplate == null)
                {
                    

                    // Navigate to xaml page
                    Uri uri = new Uri("SmartPaginator;component/Resources/ListItemElementtemplate.xaml", UriKind.Relative);
                    StreamResourceInfo info = Application.GetResourceStream(uri);
                    XamlReader reader = new XamlReader();
                    _DefaultTemplate = (ControlTemplate)reader.LoadAsync(info.Stream);
                }
                return _DefaultTemplate;
            }
        }
        private static ControlTemplate _DefaultTemplate;
        
        #endregion

        #region Marker Formatting

        private void SetupMarker()
        {
            var list = Source.List;

            this.MarkerFont = Source.FontFamily;
            this.MarkerSize = Source.FontSize;
            this.ShowMarker = false;
            this.ItemOffset = ListExtensions.GetItemOffset(list);

            if (!double.IsNaN(list.MarkerOffset))
            {
                this.MarkerOffset = list.MarkerOffset;
            }
            else
            {
                this.MarkerOffset = MarkerFont.LineSpacing * MarkerSize / 2;
            }

            if (list.MarkerStyle != TextMarkerStyle.None)
            {
                this.ShowMarker = true;
                int index = list.ListItems.ToList().IndexOf(Source) + 1;

                switch (list.MarkerStyle)
                {
                    case TextMarkerStyle.Circle:
                        this.MarkerText = "\x00a1";
                        this.MarkerFont = WINGDINGS;
                        break;

                    case TextMarkerStyle.Square:
                        this.MarkerText = "q";
                        this.MarkerFont = WINGDINGS;
                        break;

                    case TextMarkerStyle.Box:
                        this.MarkerText = "\x00a7";
                        this.MarkerFont = WINGDINGS;
                        break;

                    case TextMarkerStyle.Decimal:
                        this.MarkerText = String.Format("{0}.", index);
                        break;

                    case TextMarkerStyle.LowerLatin:
                        this.MarkerText = GetLatinText(index);
                        break;

                    case TextMarkerStyle.UpperLatin:
                        this.MarkerText = GetLatinText(index).ToUpper();
                        break;

                    case TextMarkerStyle.LowerRoman:
                        this.MarkerText = GetRomanText(index);
                        break;

                    case TextMarkerStyle.UpperRoman:
                        this.MarkerText = GetRomanText(index).ToUpper();
                        break;

                    default:
                        this.MarkerText = "\x009f";
                        this.MarkerFont = WINGDINGS;
                        break;
                }
            }
        }


        private static string GetLatinText(int index)
        {
            return GetMarkerText(index, LATIN_SYMBOLS);
        }

        private static string GetMarkerText(int index, string symbols)
        {
            // We base our calculations on a zero-based array, but the user passes a 1 based count
            index--;

            var result = new StringBuilder();
            int length = symbols.Length;

            int remaining = index;
            if (index > length)
            {
                remaining = index % length;
                result.Append(GetMarkerText(index / length, symbols));
            }

            result.Append(symbols[remaining]);
            result.Append(".");

            return result.ToString();
        }

        // Code copied from http://www.blackwasp.co.uk/NumberToRoman.aspx
        private static string GetRomanText(int index)
        {
            // Validate
            if (index < 0 || index > 3999)
                throw new ArgumentException("Value must be in the range 0 - 3,999.");

            if (index == 0) return "N";

            //Setup key numerals and numeral pairs
            int[] values = new int[] { 1000, 900, 500, 400, 100,90, 50, 40, 10, 9, 5, 4, 1 };
            string[] numerals = new string[] { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" };

            // Initialise the string builder
            var result = new StringBuilder();

            // Loop through each of the values to diminish the number
            for (int i = 0; i < 13; i++)
            {
                // If the number being converted is less than the test value, append
                // the corresponding numeral or numeral pair to the resultant string
                while (index >= values[i])
                {
                    index -= values[i];
                    result.Append(numerals[i]);
                }
            }
            result.Append(".");

            return result.ToString();
        }



        private const string LATIN_SYMBOLS = "abcdefghijklmnopqrstuvwxyz";

        protected static readonly FontFamily WINGDINGS = new FontFamily("Wingdings");

        #endregion
    }
}
